#include <inc/x86.h>
#include <inc/elf.h>

/**********************************************************************
 * This a dirt simple boot loader, whose sole job is to boot
 * an ELF kernel image from the first IDE hard disk.
 *
 * DISK LAYOUT
 *  * This program(boot.S and main.c) is the bootloader.  It should
 *    be stored in the first sector of the disk.
 *
 *  * The 2nd sector onward holds the kernel image.
 *
 *  * The kernel image must be in ELF format.
 *
 * BOOT UP STEPS
 *  * when the CPU boots it loads the BIOS into memory and executes it
 *
 *  * the BIOS intializes devices, sets of the interrupt routines, and
 *    reads the first sector of the boot device(e.g., hard-drive)
 *    into memory and jumps to it.
 *
 *  * Assuming this boot loader is stored in the first sector of the
 *    hard-drive, this code takes over...
 *
 *  * control starts in boot.S -- which sets up protected mode,
 *    and a stack so C code then run, then calls bootmain()
 *
 *  * bootmain() in this file takes over, reads in the kernel and jumps to it.
 **********************************************************************/

/*
 * boot.S这一段汇编代码将会把保护模式设定好，然后就调用下面这一段代码中的bootmain()
 * bootmain把kernel读入内存中，然后跳转到那
 */

#define SECTSIZE	512
#define ELFHDR		((struct Elf *) 0x10000) // scratch space

void readsect(void*, uint32_t);
void readseg(uint32_t, uint32_t, uint32_t);

void
bootmain(void)
{
	struct Proghdr *ph, *eph;
	int i;

	// read 1st page off disk
    // 一页为4K
    // 读取到
	readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);

	// is this a valid ELF?
	// 检查魔数
	if (ELFHDR->e_magic != ELF_MAGIC)
		goto bad;

	// load each program segment (ignores ph flags)
	// 加载elf可执行文件的各个segment
	ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
	eph = ph + ELFHDR->e_phnum;
	for (; ph < eph; ph++) {
		// p_pa is the load address of this segment (as well
		// as the physical address)
		readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
		for (i = 0; i < ph->p_memsz - ph->p_filesz; i++) {
			*((char *) ph->p_pa + ph->p_filesz + i) = 0;
		}
	}

	// call the entry point from the ELF header
	// note: does not return!
	((void (*)(void)) (ELFHDR->e_entry))();

bad:
	outw(0x8A00, 0x8A00);
	outw(0x8A00, 0x8E00);
	while (1)
		/* do nothing */;
}

// Read 'count' bytes at 'offset' from kernel into physical address 'pa'.
// Might copy more than asked
void
readseg(uint32_t pa, uint32_t count, uint32_t offset)
{
	uint32_t end_pa;

	end_pa = pa + count;

	// round down to sector boundary
	// 向低地址对齐扇区边界
	pa &= ~(SECTSIZE - 1);

	// translate from bytes to sectors, and kernel starts at sector 1
    // 计算需要的扇区数
	offset = (offset / SECTSIZE) + 1;

	// If this is too slow, we could read lots of sectors at a time.
	// We'd write more to memory than asked, but it doesn't matter --
	// we load in increasing order.
	while (pa < end_pa) {
		// Since we haven't enabled paging yet and we're using
		// an identity segment mapping (see boot.S), we can
		// use physical addresses directly.  This won't be the
		// case once JOS enables the MMU.
		readsect((uint8_t*) pa, offset);
		pa += SECTSIZE;
		offset++;
	}
}

void
waitdisk(void)
{
	// wait for disk reaady
    // 向0x1F7端口发送数据
    // 参考地址：https://my.oschina.net/wangconglin87/blog/278745
// 端口1F7H　　 0号硬盘状态寄存器（读时）、0号硬盘命令寄存器（写时）
//0 ERR,错误(ERROR),该位为1表示在结束前次的命令执行时发生了无法恢复的错误。在错误寄存器中保存了更多的错误信息。
//1 IDX,反映从驱动器读入的索引信号。
//2 CORR,该位为1时,表示已按ECC算法校正硬盘的读数据。
//3 DRQ,为1表示请求主机进行数据传输(读或写)。
//4 DSC,为1表示磁头完成寻道操作,已停留在该道上。
//5 DF,为1时,表示驱动器发生写故障。
//6 DRDY,为1时表示驱动器准备好,可以接受命令。
//7 BSY,为1时表示驱动器忙(BSY),正在执行命令。在发送命令前先判断该位
// 0xC0 = 1100 0000 
// 0x40 = 0100 0000
// 当6号位是1, 7号位是0时, 跳出循环
	while ((inb(0x1F7) & 0xC0) != 0x40)
		/* do nothing */;
}

void
readsect(void *dst, uint32_t offset)
{
    // offset 表示的是读取的扇区号
    // dst 表示读取之后存放到内存中的地址
	// wait for disk to be ready
	waitdisk();

    // 下面用到的端口在 notes/ports.md中有记录

    // 设置需要读取的扇区数
	outb(0x1F2, 1);		// count = 1

    // 0x1F{3,4,5,}都是用于表明数据在磁盘中的地址的
    // 可以是LBA表示，也可能是CHS表示
    // 具体见 notes/ports
	outb(0x1F3, offset);
	outb(0x1F4, offset >> 8);
	outb(0x1F5, offset >> 16);
    // 磁头
	outb(0x1F6, (offset >> 24) | 0xE0);

    // 发送读取命令
	outb(0x1F7, 0x20);	// cmd 0x20 - read sectors

	// wait for disk to be ready
	waitdisk();

	// read a sector
    // 在x86.h中, 使用一个嵌入汇编的内联函数
    // 从端口1F0H中读取数据到dst中, 每次4字节, 读取0x80次
	insl(0x1F0, dst, SECTSIZE/4);
}
